/**
 * Copyright(c) 2013 Jade Techonologies Co., Ltd.
 *
 * History:
 *   13-4-14 下午9:34 Created by dwzhang
 */
package com.jade.framework.base.lang;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;

import org.apache.commons.collections.map.MultiKeyMap;
import org.apache.commons.io.IOUtils;

/**
 * 采用服务定位的方式定位的类（根据Sun比较常用的 "META-INF/services/类名.service"的文件来存放相应的实现）.
 *
 * @author dwzhang
 * @version 1.0 13-4-14 下午9:34
 */
public class ServiceLocator
{
    private static final String PREFIX = "META-INF/services/";
    //为什么加这个后缀？因为IDEA每次用文本方式打开会将后缀写入配置
    private static final String SERVICE_SUFFIX = ".service";
    private static final String PROPERTIES_SUFFIX = ".properties";

    private static Map<Class<?>, Object> serviceCache = new HashMap<Class<?>, Object>(64);
    private static MultiKeyMap namedServiceCache = new MultiKeyMap();
    //private static DoubleKeyMap<Class, String, Object> namedServiceCache
    //        = new DoubleKeyHashMap<Class, String, Object>(32);
    private static Map<String, Class<?>> allImplementes = new HashMap<String, Class<?>>();
    // Service --> Properties
    private static Map<Class, Properties> servicePrperties = new HashMap<Class, Properties>();

    private ServiceLocator()
    {
    }

    private static void fail(Class service, String msg, Throwable cause)
            throws IllegalArgumentException
    {
        IllegalArgumentException sce = new IllegalArgumentException(service.getName() + ": " + msg);

        throw (IllegalArgumentException)initCause(sce, cause);
    }

    /**
     * Init the cause field of a Throwable object.
     * The cause field is set only if <var>t</var> has an
     * {@link Throwable#initCause(Throwable)} method (JDK Version >= 1.4)
     *
     * @param t     Throwable on which the cause must be set.
     * @param cause The cause to set on <var>t</var>.
     * @return <var>t</var> with or without the cause field set.
     */
    private static Throwable initCause(Throwable t, Throwable cause)
    {
        /* Make a best effort to set the cause, but if we don't
           succeed, too bad, you don't get that useful debugging
           information.  We jump through hoops here so that we can
           work on platforms prior to J2SE 1.4 where the
           Throwable.initCause method was introduced.  If we change
           the public interface of JMRuntimeException in a future
           version we can add getCause() so we don't need to do this.  */
        try {
            java.lang.reflect.Method initCause = t.getClass().getMethod("initCause", new Class[]{Throwable.class});
            initCause.invoke(t, cause);
        }
        catch (Exception e) {
            // OK.
            // too bad, no debugging info
        }
        return t;
    }

    private static void fail(Class service, String msg)
            throws IllegalArgumentException
    {
        throw new IllegalArgumentException(service.getName() + ": " + msg);
    }

    private static void fail(Class service, URL u, int line, String msg)
            throws IllegalArgumentException
    {
        fail(service, u + ":" + line + ": " + msg);
    }

    /**
     * Parse a single line from the given configuration file, adding the name
     * on the line to both the names list and the returned set iff the name is
     * not already a member of the returned set.
     */
    private static int parseLine(Class service, URL u, BufferedReader r, int lc, List<String> names,
            Set<String> returned)
            throws IOException, IllegalArgumentException
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) {
            ln = ln.substring(0, ci);
        }
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) {
                fail(service, u, lc, "Illegal configuration-file syntax");
            }
            if (!Character.isJavaIdentifierStart(ln.charAt(0))) {
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            for (int i = 1; i < n; i++) {
                char c = ln.charAt(i);
                if (!Character.isJavaIdentifierPart(c) && (c != '.')) {
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
                }
            }
            if (!returned.contains(ln)) {
                names.add(ln);
                returned.add(ln);
            }
        }
        return lc + 1;
    }

    /**
     * Parse the content of the given URL as a provider-configuration file.
     *
     * @param service  The service class for which providers are being sought;
     *                 used to construct error detail strings
     * @param u        The URL naming the configuration file to be parsed
     * @param returned A Set containing the names of provider classes that have already
     *                 been returned.  This set will be updated to contain the names
     *                 that will be yielded from the returned <tt>Iterator</tt>.
     * @return A (possibly empty) <tt>Iterator</tt> that will yield the
     *         provider-class names in the given configuration file that are
     *         not yet members of the returned set
     * @throws IllegalArgumentException If an I/O error occurs while reading from the given URL, or
     *                                  if a configuration-file format error is detected
     */
    private static Iterator parse(Class service, URL u, Set<String> returned)
            throws IllegalArgumentException
    {
        InputStream in = null;
        BufferedReader r = null;
        List<String> names = new ArrayList<String>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names, returned)) >= 0) {
            }
        }
        catch (IOException x) {
            fail(service, ": " + x);
        }
        finally {
            IOUtils.closeQuietly(r);
            IOUtils.closeQuietly(in);
        }
        return names.iterator();
    }


    /** Private inner class implementing fully-lazy provider lookup */
    private static final class LazyIterator<T> implements Iterator<Class<?>>
    {
        Class<T> service;
        ClassLoader loader;
        Enumeration configs = null;
        Iterator pending = null;
        Set<String> returned = new TreeSet<String>();
        String nextName = null;

        private LazyIterator(Class<T> service, ClassLoader loader)
        {
            this.service = service;
            this.loader = loader;
        }

        public boolean hasNext()
                throws IllegalArgumentException
        {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName() + SERVICE_SUFFIX;
                    if (loader == null) {
                        configs = ClassLoaderUtils.getResources(fullName);
                    }
                    else {
                        configs = loader.getResources(fullName);
                    }
                }
                catch (IOException x) {
                    fail(service, ": " + x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, (URL)configs.nextElement(), returned);
            }
            nextName = (String)pending.next();
            return true;
        }

        public Class<?> next()
                throws IllegalArgumentException
        {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            String cn = nextName;
            nextName = null;
            return loadClass(cn, service, loader);
        }

        public void remove()
        {
            throw new UnsupportedOperationException();
        }

    }

    private static <T> Class<?> loadClass(String cn, Class<T> service, ClassLoader loader)
    {
        if (loader == null) {
            loader = ClassLoaderUtils.getClassLoader();
        }
        try {
            return Class.forName(cn, true, loader);
        }
        catch (ClassNotFoundException x) {
            fail(service, "Provider " + cn + " not found");
        }
        catch (Exception x) {
            fail(service, "Provider " + cn + " could not be instantiated: " + x, x);
        }
        return null;
    }

    /**
     * 查找一个合适的实现类，并创建对象，默认作为单例来处理
     *
     * @param service 服务接口类
     * @return 对象实例
     */
    public static <T> T getInstance(Class<T> service)
    {
        return getInstance(service, true);
    }

    /**
     * 查找一个合适的实现类，并创建对象，默认作为单例来处理
     *
     * @param service 服务接口类
     * @param loader  类装载器
     * @return 对象实例
     */
    public static <T> T getInstance(Class<T> service, ClassLoader loader)
    {
        Class<?> clazz = getImplement(service, loader);
        return createInstance(clazz, true, service);
    }

    /**
     * 查找一个合适的实现类，并创建对象
     *
     * @param service   服务接口类
     * @param singleton 是否是单例（如果是单例，会将该Service Cache在ServiceLocator中，并且优先调用#getInstance()方法）
     * @return 对象实例
     */
    public static <T> T getInstance(Class<T> service, boolean singleton)
    {
        return getInstance(service, singleton, ClassLoaderUtils.getClassLoader());
    }

    /**
     * 查找一个合适的实现类
     *
     * @param service   服务接口类
     * @param loader    类装载器
     * @param singleton 是否是单例（如果是单例，会将该Service Cache在ServiceLocator中，并且优先调用#getInstance()方法）
     * @return 对象实例
     */
    public static <T> T getInstance(Class<T> service, boolean singleton, ClassLoader loader)
    {
        Class<?> clazz = getImplement(service, loader);
        return createInstance(clazz, singleton, service);
    }

    /**
     * 查找一个合适的实现类
     *
     * @param service 服务接口类
     * @return 对象实例
     */
    public static <T> Class<?> getImplement(Class<T> service)
    {
        return getImplement(service, ClassLoaderUtils.getClassLoader());
    }

    /**
     * 查找一个合适的实现类（第一个匹配成功返回）
     *
     * @param service 服务接口类
     * @param loader  类装载器
     * @return 对象实例
     */
    public static <T> Class<?> getImplement(Class<T> service, ClassLoader loader)
    {
        String key = service + loader.toString();
        Class<?> first = allImplementes.get(key);
        if (first == null) {
            synchronized (service) {
                first = allImplementes.get(key);
                if (first == null) {
                    Iterator<Class<?>> it = getImplements(service, loader);
                    //取第一个
                    while (it.hasNext()) {
                        Class<?> clazz = it.next();
                        if (service.isAssignableFrom(clazz)) {
                            first = clazz;
                            break;
                        }
                    }
                    if (first != null) {
                        allImplementes.put(key, first);
                    }
                }
            }

            if (first == null) {
                fail(service, "The class never implemented the service");
            }
        }
        return first;
    }

    /**
     * 返回该接口实现的所有类枚举
     *
     * @param service The service's abstract service class
     * @return 类的枚举
     * @throws IllegalArgumentException If a provider-configuration file violates the specified format
     *                                  or names a provider class that cannot be found and instantiated
     */
    public static <T> Iterator<Class<?>> getImplements(Class<T> service)
            throws IllegalArgumentException
    {
        return getImplements(service, ClassLoaderUtils.getClassLoader());
    }

    /**
     * 返回该接口实现的所有类枚举
     *
     * @param service The service's abstract service class
     * @param loader  类装载器
     * @return 类的枚举
     * @throws IllegalArgumentException If a provider-configuration file violates the specified format
     *                                  or names a provider class that cannot be found and instantiated
     */
    public static <T> Iterator<Class<?>> getImplements(Class<T> service, ClassLoader loader)
            throws IllegalArgumentException
    {
        return new LazyIterator<T>(service, loader);
    }

    //可以根据名称来返回实例或者类的机制（采用.properties文件作为存放定位信息的格式）

    /**
     * 查找一个合适的实现类，并创建对象
     *
     * @param service 服务接口类
     * @return 对象实例
     */
    public static <T> T getInstance(String name, Class<T> service)
    {
        return getInstance(name, service, ClassLoaderUtils.getClassLoader());
    }


    /**
     * 查找一个合适的实现类，并创建对象，默认作为单例来处理
     *
     * @param service 服务接口类
     * @param loader  类装载器
     * @return 对象实例
     */
    public static <T> T getInstance(String name, Class<T> service, ClassLoader loader)
    {
        return getInstance(name, service, true, loader);
    }

    /**
     * 查找一个合适的实现类，并创建对象，默认作为单例来处理
     *
     * @param service   服务接口类
     * @param singleton 是否是单例（如果是单例，会将该Service Cache在ServiceLocator中，并且优先调用#getInstance()方法）
     * @return 对象实例
     */
    public static <T> T getInstance(String name, Class<T> service, boolean singleton)
    {
        return getInstance(name, service, singleton, ClassLoaderUtils.getClassLoader());
    }


    /**
     * 查找一个合适的实现类
     *
     * @param service   服务接口类
     * @param singleton 是否是单例（如果是单例，会将该Service Cache在ServiceLocator中，并且优先调用#getInstance()方法）
     * @param loader    类装载器
     * @return 对象实例
     */
    public static <T> T getInstance(String name, Class<T> service, boolean singleton, ClassLoader loader)
    {
        if (name == null) {
            throw new IllegalArgumentException("Null name");
        }
        if (service == null) {
            throw new IllegalArgumentException("Can't get instance from Null Service class");
        }
        T instance = null;
        if (singleton) {
            instance = (T)namedServiceCache.get(service, name);
            if (instance == null) {
                synchronized (service) {
                    instance = (T)namedServiceCache.get(service, name);
                    if (instance == null) {
                        Class<?> clazz = getImplement(name, service, loader);
                        instance = createInstance0(clazz, singleton, service);
                        //                        //自动注入对象名称
                        //                        if (instance instanceof Namable) {
                        //                            ((Namable)instance).setName(name);
                        //                        }
                        namedServiceCache.put(service, name, instance);
                    }
                }
            }
        }
        else {
            Class<?> clazz = getImplement(name, service, loader);
            instance = createInstance0(clazz, singleton, service);
            //自动注入对象名称
            //            if (instance instanceof Namable) {
            //                ((Namable)instance).setName(name);
            //            }
        }
        return instance;
    }

    /**
     * 创建实例
     *
     * @param clazz     实际实现类
     * @param singleton 是否是单例（如果是单例，会将该Service Cache在ServiceLocator中，并且优先调用#getInstance()方法）
     * @param service   服务
     * @return 创建实例
     */
    private static <T> T createInstance(Class<?> clazz, boolean singleton, Class<T> service)
    {
        if (clazz != null) {
            T obj = null;
            if (singleton) {
                obj = (T)serviceCache.get(service);
                if (obj == null) {
                    synchronized (service) {
                        obj = (T)serviceCache.get(service);
                        if (obj == null) {
                            obj = createInstance0(clazz, singleton, service);
                            serviceCache.put(service, obj);
                        }
                    }
                }
                return obj;
            }
            else {
                return createInstance0(clazz, singleton, service);
            }
        }
        throw new IllegalStateException("No implement of the service " + service);
    }

    /**
     * 创建实例
     *
     * @param clazz     实际实现类
     * @param singleton 是否是单例（如果是单例，会将该Service Cache在ServiceLocator中，并且优先调用#getInstance()方法）
     * @param service   服务
     * @return 创建实例
     */
    private static <T> T createInstance0(Class<?> clazz, boolean singleton, Class<T> service)
    {
        if (clazz != null) {
            if (singleton) {
                try {
                    return (T)ReflectUtils.newSingleton(clazz);
                }
                catch (Exception e) {
                    fail(service, "Provider " + clazz.getName() + " could not be instantiated: " + e, e);
                }
            }
            else {
                try {
                    return (T)ReflectUtils.newInstance(clazz);
                }
                catch (Exception e) {
                    fail(service, "Provider " + clazz.getName() + " could not be instantiated: " + e, e);
                }
            }
        }
        throw new IllegalStateException("No implement of the service " + service);
    }

    /**
     * 查找一个合适的实现类
     *
     * @param service 服务接口类
     * @return 实现类
     */
    public static <T> Class<?> getImplement(String name, Class<T> service)
    {
        return getImplement(name, service, ClassLoaderUtils.getClassLoader());
    }

    /**
     * 查找一个合适的实现类
     *
     * @param service 服务接口类
     * @param loader  类装载器
     * @return 实现类
     */
    public static <T> Class<?> getImplement(String name, Class<T> service, ClassLoader loader)
    {
        Properties props = getImplementProperties0(service, loader, true);
        if (props != null) {
            String className = props.getProperty(name);
            if (className == null) {
                throw new IllegalStateException("No such implement of " + " service:" + service + " by name:" + name);
            }
            Class<?> clazz = loadClass(className, service, loader);
            if (!service.isAssignableFrom(clazz)) {
                throw new IllegalStateException("The " + clazz + " is not implements " + " service:" + service);
            }
            return clazz;
        }
        throw new IllegalStateException("No implement of the service " + service + " name:" + name);
    }

    /**
     * 根据资源名称装载属性
     *
     * @param resourceName 资源名称
     * @return 属性集合
     */
    private static Properties loadProperties(String resourceName, ClassLoader loader)
            throws IOException
    {
        Thread thread = Thread.currentThread();
        ClassLoader oldLoader = thread.getContextClassLoader();
        thread.setContextClassLoader(loader);
        try {
            Enumeration en = ClassLoaderUtils.getResources(resourceName);
            Properties properties = new Properties();
            while (en.hasMoreElements()) {
                URL url = (URL)en.nextElement();
                InputStream input = url.openStream();
                try {
                    properties.load(input);
                }
                finally {
                    IOUtils.closeQuietly(input);
                }
            }
            return properties;
        }
        finally {
            thread.setContextClassLoader(oldLoader);
        }
    }

    /**
     * 根据服务类找出所有的实现映射
     *
     * @param service 服务
     * @return 实现的属性集合
     */
    public static final Properties getImplementProperties(Class service)
    {
        return getImplementProperties(service, ClassLoaderUtils.getClassLoader());
    }

    /**
     * 根据服务类找出所有的实现映射
     *
     * @param service 服务
     * @param cache   是否Cache相应的Properties
     * @return 实现的属性集合
     */
    public static final Properties getImplementProperties(Class service, boolean cache)
    {
        return getImplementProperties(service, ClassLoaderUtils.getClassLoader(), cache);
    }

    /**
     * 根据服务类找出所有的实现映射
     *
     * @param service 服务
     * @param loader  装载器
     * @return 是否Cache
     */
    private static Properties getImplementProperties0(Class service, ClassLoader loader, boolean cache)
    {
        if (cache) {
            Properties props = (Properties)servicePrperties.get(service);
            if (props != null) {
                return props;
            }
            String resourceName = PREFIX + service.getName() + PROPERTIES_SUFFIX;
            try {
                props = loadProperties(resourceName, loader);
            }
            catch (IOException e) {
                //do nothing
            }
            if (props != null) {
                servicePrperties.put(service, props);
            }
            return props;
        }
        else {
            String resourceName = PREFIX + service.getName() + PROPERTIES_SUFFIX;
            try {
                return loadProperties(resourceName, loader);
            }
            catch (IOException e) {
                return null;
            }
        }
    }

    /**
     * 根据服务类找出所有的实现映射
     *
     * @param service 服务
     * @param loader  装载器
     * @return 属性集合
     */
    public static final Properties getImplementProperties(Class service, ClassLoader loader)
    {
        return getImplementProperties0(service, loader, true);
    }

    /**
     * 根据服务类找出所有的实现映射
     *
     * @param service 服务
     * @param loader  装载器
     * @param cache   是否Cache
     * @return 属性集合
     */
    public static final Properties getImplementProperties(Class service, ClassLoader loader, boolean cache)
    {
        return getImplementProperties0(service, loader, cache);
    }
}
